-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local skynet = require 'skynet'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local skynet = require 'skynet'
local c_factory = require 'mc.orm.factory'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local c_network_adapter = require 'device.class.network_adapter'
local card_init = require 'device.class.nic_mgmt.card.card_init'
local comm_defs = require 'device.class.nic_mgmt.comm_defs'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'
local client = require 'network_adapter.client'
local fructl = require 'infrastructure.fructl'
local log_collector = require 'device.class.log_collector'

local card_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local prop_table = {
    VendorId = 'VendorID',
    DeviceId = 'DeviceID',
    SubSystemVendorId = 'SubsystemVendorID',
    SubSystemDeviceId = 'SubsystemDeviceID',
    SocketId = 'SocketId',
    DevBus = 'DevBus',
    Bus = 'Bus',
    Device = 'Device',
    Function = 'Function',
    DevDevice = 'DevDevice',
    DevFunction = 'DevFunction',
    Slot = 'SlotNumber',
    Manufacturer = 'Manufacturer',
    PartNumber = 'PartNumber',
    PcbVersion = 'PCBVersion',
    SerialNumber = 'SerialNumber',
    PredictiveFault = 'PredictiveFault',
    DiagnosticFault = 'DiagnosticFault',
    SpecialPcieCard = 'SpecialPcieCard',
    HotSwappable = 'HotPluggable',
    ReadyToRemove = 'ReadyToRemove',
    LinkSpeed = 'LinkSpeed',
    LinkSpeedCapability = 'LinkSpeedCapability',
    LinkWidth = 'LinkWidth',
    LinkWidthAbility = 'LinkWidthCapability',
    Type = 'Type',
    DeviceName = 'DeviceLocator',
    NetworkPortCount = 'NetworkPortCount',
    ChipModel = 'Model',
    ChipVendor = 'ChipManufacturer',
    SupportedMctp = 'SupportedMctp',
    SupportedLldp = 'SupportedLLDP',
    PfMacInfo = 'PfMacInfo',
    MPUBusyStatus = 'MPUBusyStatus',
    TemperatureCelsius = 'TemperatureCelsius',
    TemperatureStatus = 'TemperatureStatus',
    MaxSFPTemperatureCelsius = 'SFPMaxTemperatureCelsius',
    Health = 'Health',
    FaultState = 'FaultState',
    FaultCode = 'FaultCode',
    Id = 'BoardID',
    RefChip = 'RefChip'
}

local device_interface_list = {
    comm_defs.PCIE_DEVICE_INTERFACE,
    comm_defs.PCIE_CARD_DEVICE_INTERFACE,
    comm_defs.PCIE_DEVICE_HOT_SWAP_INTERFACE,
    comm_defs.PCIE_DEVICE_BANDWIDTH_INTERFACE,
    comm_defs.BOARD_DEVICE_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_COOLING_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_FAULT_STATUS_INTERFACE,
    comm_defs.NETWORK_ADAPTER_DEVICE_LOG_COLLECTION_INTERFACE
}

local function get_log_by_smbus(bus, card_orm_obj, device_path)
    local device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.NETWORK_ADAPTER_DEVICE_LOG_COLLECTION_INTERFACE)
    if not device_obj then
        log:error('get_log_by_smbus failed, device_obj is nil, device_path is %s', device_path)
        return
    end
    local log_dir = log_collector.get_smbus_log_dir(card_orm_obj)
    if not log_dir then
        log:error('get_log_by_smbus failed, log_dir is nil, device_path is %s', device_path)
        return
    end
    if not log_collector.create_dir(log_dir) then
        log:error('get_log_by_smbus failed, create log_dir failed, device_path is %s', device_path)
        return
    end
    local ok, res = pcall(function()
        return device_obj:call_method('CollectLogBySmbus', 's', log_dir)
    end)
    if not ok then
        log:error('get_log_by_smbus failed, err is %s, device_path is %s', res, device_path)
        return
    end
    if not res then
        log:error('get_log_by_smbus failed, device_path is %s', device_path)
    end
end

local function get_log_by_ncsi(bus, card_orm_obj, device_path)
    local device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.NETWORK_ADAPTER_DEVICE_LOG_COLLECTION_INTERFACE)
    if not device_obj then
        log:error('get_log_by_ncsi failed, device_obj is nil, device_path is %s', device_path)
        return
    end

    local available = card_orm_obj:wait_mpu_idle()
    if not available then
        log:warning('mpu is not available, device_path is %s', device_path)
        return
    end

    local log_dir = log_collector.get_ncsi_log_dir(card_orm_obj)
    if not log_dir then
        log:error('get_log_by_ncsi failed, log_dir is nil, device_path is %s', device_path)
        return
    end
    if not log_collector.create_dir(log_dir) then
        log:error('get_log_by_ncsi failed, create log_dir failed, device_path is %s', device_path)
        return
    end

    local ok, res = pcall(function()
        return device_obj:call_method('CollectLogByNcsi', 's', log_dir)
    end)
    if not ok then
        log:error('get_log_by_ncsi failed, err is %s, device_path is %s', res, device_path)
        return
    end
    if not res then
        log:error('get_log_by_ncsi failed, device_path is %s', device_path)
    end
end

local function collect_log_by_smbus(bus, card_orm_obj, device_path)
    if card_orm_obj.smbus_collect_status ~= comm_defs.LOG_DUMP_IDLE then
        log:warning('smbus log collection is already in progress, device_path is %s', device_path)
        return
    end
    log:notice('collect_log_by_smbus start, device_path is %s', device_path)
    card_orm_obj.smbus_collect_status = comm_defs.LOG_DUMP_BUSY
    get_log_by_smbus(bus, card_orm_obj, device_path)
    card_orm_obj.smbus_collect_status = comm_defs.LOG_DUMP_IDLE
    log:notice('collect_log_by_smbus end, device_path is %s', device_path)
end

local function collect_log_by_ncsi(bus, card_orm_obj, device_path)
    if card_orm_obj.ncsi_collect_status ~= comm_defs.LOG_DUMP_IDLE then
        log:warning('ncsi log collection is already in progress, device_path is %s', device_path)
        return
    end
    log:notice('collect_log_by_ncsi start, device_path is %s', device_path)
    card_orm_obj.ncsi_collect_status = comm_defs.LOG_DUMP_BUSY
    get_log_by_ncsi(bus, card_orm_obj, device_path)
    card_orm_obj.ncsi_collect_status = comm_defs.LOG_DUMP_IDLE
    log:notice('collect_log_by_ncsi end, device_path is %s', device_path)
end

local function handle_reset_os(bus, device_path)
    log:notice('handle_reset_os, device_path is %s', device_path)
    local device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE)
    if not device_obj then
        log:error('handle_reset_os failed, device_obj is nil, device_path is %s', device_path)
        return
    end
    local ok, err = pcall(function()
        device_obj:call_method('HandleOsResetSignal', '')
    end)
    if not ok then
        log:error('handle_reset_os failed, err: %s, device_path is %s', err, device_path)
    end
end

local function listen_bmc_os_reset_signal(bus, card_orm_obj, device_path)
    -- BMC重启
    card_orm_obj:connect_signal(log_collector.log_dump_reset_bmc_sig, function()
        card_orm_obj:next_tick(function()
            log:notice('receive bmc reset signal, collect log by smbus, device_path is %s', device_path)
            if not card_orm_obj.smbus_has_collected then
                collect_log_by_smbus(bus, card_orm_obj, device_path)
            end
            log:notice('receive bmc reset signal, collect log by ncsi, device_path is %s', device_path)
            collect_log_by_ncsi(bus, card_orm_obj, device_path)
        end)
    end)
    -- OS重启
    card_orm_obj:connect_signal(log_collector.log_dump_reset_smbios_sig, function()
        card_orm_obj:next_tick(function()
            -- 通知devmon处理os重启信号
            handle_reset_os(bus, device_path)
            skynet.sleep(12000) -- 等待两分钟再收集，避免网卡还未上电完成导致收集失败
            log:notice('receive os reset signal, collect log by smbus, device_path is %s', device_path)
            collect_log_by_smbus(bus, card_orm_obj, device_path)
            log:notice('receive os reset signal, collect log by ncsi, device_path is %s', device_path)
            collect_log_by_ncsi(bus, card_orm_obj, device_path)
        end)
    end)
end

-- 处理特殊的属性映射逻辑
local function handle_special_property_mapping(interface, prop, orm_obj, value)
    if interface == comm_defs.PCIE_DEVICE_INTERFACE and prop == 'Location' then
        orm_obj['Position'] = value
    end
    if interface == comm_defs.PCIE_CARD_DEVICE_INTERFACE and prop == 'Name' then
        orm_obj['Name'] = value
    end
    if interface == comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE and prop == 'Description' then
        orm_obj['Description'] = value
        orm_obj['ModelDescription'] = value
    end
    if interface == comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE and prop == 'FirmwareVersion' then
        orm_obj['FirmwareVersion'] = value
        -- 网卡版本更新后，同步到该网卡的所有网口
        local ports = orm_obj.children
        for _, port in pairs(ports) do
            port.FirmwareVersion = value
        end
    end
end

local function listen_device_obj_property_change(bus, sig_slot, card_device_path, orm_obj)
    log:notice('listen_device_obj_property_change start, card_device_path is %s', card_device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        card_device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            pcall(function()
                if prop_table[k] then
                    orm_obj[prop_table[k]] = v:value()
                else
                    handle_special_property_mapping(interface, k, orm_obj, v:value())
                end
                if interface == comm_defs.NETWORK_ADAPTER_DEVICE_FAULT_STATUS_INTERFACE and k == 'FaultState' then
                    -- 网卡产生故障后，触发收集日志
                    if v:value() ~= 0 then
                        collect_log_by_smbus(bus, orm_obj, card_device_path)
                        orm_obj.smbus_has_collected = true
                    end
                end
            end)
        end
    end)
end

local function get_node_id_by_device_path(bus, device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path, { comm_defs.PCIE_DEVICE_INTERFACE })
    local card_device_obj
    for _, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            card_device_obj = mdb.get_object(bus, device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not card_device_obj then
        log:notice('get_node_id_by_device_path failed, device_path is %s', device_path)
        return
    end
    local ret, device_name = card_device_obj:get_property('DeviceName')
    if ret ~= 0 then
        log:error('get_node_id_by_device_path failed, device_path is %s', device_path)
        return
    end
    local node_id = string.gsub(device_name, '%s+', '')
    return node_id
end

local function create_card_orm_object(bus, device_path)
    log:notice('create_card_orm_object start, device_path is %s', device_path)
    local node_id = get_node_id_by_device_path(bus, device_path)
    if not node_id then
        log:error('create_card_orm_object failed, because node_id is nil, device_path is %s', device_path)
        return
    end
    local object_name = comm_fun.get_object_name_by_device_path(bus, device_path, 
        {from = "PCIeNicCard", to = "NetworkAdapter"})
    if not object_name then
        log:error('create_card_orm_object failed, because object_name is nil, device_path is %s', device_path)
        return
    end
    local mdb_obj = c_network_adapter.create_mdb_object({
        ID = node_id,
        NodeId = node_id,
        ObjectName = object_name,
        CreatedByDeviceObject = true -- 标识orm对象来源于设备树对象
    })
    c_factory.get_instance():create_object('NetworkAdapter', mdb_obj)
    local card_orm_obj = c_network_adapter.collection:find({
        ID = node_id,
        NodeId = node_id
    })
    if card_orm_obj then
        log:notice('create_card_orm_object success, device_path is %s', device_path)
    else
        log:error('create_card_orm_object failed, device_path is %s', device_path)
    end
    return card_orm_obj
end

function card_mgmt:on_add_card_device_obj()
    log:notice('on_add_card_device_obj start')
    local self_instance = self
    comm_fun.set_interface_add_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE, function(path)
            self_instance:init_obj(path)
        end)
end

local function init_os_power_state(bus, device_path)
    local card_device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE)
    if not card_device_obj then
        log:error('init_os_power_state failed, device_path is %s', device_path)
        return
    end
    client:OnFruCtrlPropertiesChanged(function(values, _, _)
        if values.PowerState then
            local power_state = values.PowerState:value()
            card_device_obj.OSPowerState = power_state == 'ON' and 1 or 0
            log:notice('os power state changed to %s, device_path is %s', card_device_obj.OSPowerState, device_path)
        end
    end)
    card_device_obj.OSPowerState = fructl.get_power_status() == 'ON' and 1 or 0
    log:notice('os power state set to %s, device_path is %s', card_device_obj.OSPowerState, device_path)
end

local function get_device_name(bus, device_path)
    local pcie_device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.PCIE_DEVICE_INTERFACE)
    if not pcie_device_obj then
        log:error('get_device_name failed to get pcie device obj, device_path is %s', device_path)
        return
    end
    return pcie_device_obj.DeviceName
end

-- 根据名称查找Component对象，带重试机制
local function get_component_by_name_with_retry(bus, device_path)
    local device_name = get_device_name(bus, device_path)
    if not device_name then
        return
    end
    local max_retries = 20
    local obj
    for _ = 1, max_retries do
        client:ForeachComponentObjects(function(component_obj)
            if component_obj.Name == device_name then
                obj = component_obj
            end
        end)

        if obj then
            log:notice('found matching component %s', device_name)
            return obj
        end
        skynet.sleep(300)
    end
end

-- 根据路径和名称查找Component对象
local function get_component_by_path_and_name(bus, path, device_path)
    local device_name = get_device_name(bus, device_path)
    if not device_name then
        return
    end
    local obj = client:GetComponentObjects()[path]
    if obj and obj.Name == device_name then
        return obj
    end
end

local function init_component_health(bus, device_path)
    local card_device_obj = comm_fun.get_device_obj_by_path_interface(bus, device_path,
        comm_defs.PCIE_CARD_DEVICE_INTERFACE)
    if not card_device_obj then
        log:error('init component health failed, device_path is %s', device_path)
        return
    end

    -- 设置Component属性变化监听
    client:OnComponentPropertiesChanged(function(_, path, _)
        -- 通过path和名称获取Component对象
        local component_obj = get_component_by_path_and_name(bus, path, device_path)
        if component_obj then
            card_device_obj.Health = component_obj.Health
            log:notice('Health changed to %s for matching component %s', component_obj.Health, component_obj.Name)
        end
    end)

    -- 更新设备Health值从匹配的Component对象
    local component_obj = get_component_by_name_with_retry(bus, device_path)
    if component_obj then
        card_device_obj.Health = component_obj.Health
        log:notice('Health updated from matching component %s to %s', component_obj.Name, component_obj.Health)
    end
end

local function synchronize_property(bus, card_device_path, orm_obj)
    log:notice('synchronize_property start, card_device_path is %s', card_device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        card_device_path,
        device_interface_list)
    local card_device_obj, props
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            card_device_obj = mdb.get_object(bus, card_device_path, interface)
            props = bus:call(service_name, card_device_path,
                comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                pcall(function()
                    local ret, val = card_device_obj:get_property(prop)
                    if ret ~= 0 then
                        log:error('get_property failed, prop=%s, device_path=%s', prop, card_device_path)
                        goto continue
                    end
                    if prop_table[prop] then
                        orm_obj[prop_table[prop]] = val
                    else
                        handle_special_property_mapping(interface, prop, orm_obj, val)
                    end
                    ::continue::
                end)
            end
        end
    end
end

function card_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个网卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then return end
        local card_orm_obj = create_card_orm_object(self.bus, device_path)
        if card_orm_obj then
            self.objects[device_path] = card_orm_obj
            -- 保存device_path到ORM对象中，供后续使用
            card_orm_obj.device_path = device_path
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            listen_device_obj_property_change(self.bus, self.sig_slot, device_path, card_orm_obj)
            -- 再将设备树对象属性赋值给对应资源树对象
            synchronize_property(self.bus, device_path, card_orm_obj)
            -- 监听并设置设备树网卡对象的上下电状态
            init_os_power_state(self.bus, device_path)
            -- 监听并设置设备树网卡Health
            init_component_health(self.bus, device_path)
            -- 监听os和BMC重启信号
            listen_bmc_os_reset_signal(self.bus, card_orm_obj, device_path)
        end
    end)
end

-- 网口资源树对象创建后，注册到网卡的子对象集合中
function card_mgmt:register_children(port_orm_obj, card_orm_obj)
    table.insert(card_orm_obj.children, port_orm_obj)
    -- 当网卡所有的网口子对象创建好后再初始化
    if #card_orm_obj.children == card_orm_obj.NetworkPortCount then
        log:notice('card all ports ready, node_id is %s', card_orm_obj.NodeId)
        card_init.init(card_orm_obj)
    end
end

function card_mgmt:get_orm_obj_by_device_path(card_device_path)
    local card_orm_obj = self.objects[card_device_path]
    if not card_orm_obj then
        log:error(
            'there is not orm obj for card device obj, card_device_path is %s',
            card_device_path)
        return
    end
    return card_orm_obj
end

function card_mgmt:ctor(bus)
    self.bus = bus
    self.objects = {}  -- 网卡设备树对象和资源树对象的映射关系
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function card_mgmt:init()
    log:notice('card_mgmt init')
    -- 先监听网卡设备树对象新增信号
    self:on_add_card_device_obj()
    -- 获取所有的网卡设备树对象，创建对应的orm对象
    local card_device_paths = comm_fun.get_all_device_paths(self.bus, comm_defs.CARD_DEVICE_PATH_PATTERN, 1,
        comm_defs.NETWORK_ADAPTER_DEVICE_INTERFACE)
    for _, card_device_path in pairs(card_device_paths) do
        log:notice('get card_device_path: %s', card_device_path)
        self:init_obj(card_device_path)
    end
end

return singleton(card_mgmt)
